# @Time : 2025/4/19 1:02
# @Author : ZhangQi
import os
import random
import re
import subprocess
import time
from datetime import datetime
from typing import Optional, Dict

from adb.adb_decorator import adb_function
from agent.adb_agent import ADBAgent
from common.all_exception import ElementNotFound


class DeviceManager(ADBAgent):
    """设备管理功能"""

    @adb_function({
        "name": "screenshot",
        "description": "截取屏幕截图并保存到指定路径",
        "parameters": {
            "type": "object",
            "properties": {
                "save_path": {
                    "type": "string",
                    "description": "截图保存路径",
                    "default": "screenshot.png"
                }
            }
        }
    })
    def screenshot(self, save_path: str = "screenshot.png"):
        self.execute_adb("shell screencap -p /sdcard/temp.png")
        self.execute_adb(f"pull /sdcard/temp.png {save_path}")
        return {"path": save_path}

    @adb_function({
        "name": "input_text",
        "description": "在焦点输入框输入文本并下移光标",
        "parameters": {
            "type": "object",
            "properties": {
                "text": {"type": "string", "description": "要输入的文本内容"},
                "keycode": {
                    "type": "string",
                    "enum": ["KEYCODE_TAB", "KEYCODE_DPAD_DOWN"],
                    "default": "KEYCODE_TAB",
                    "description": "使用的按键代码（默认TAB键）"
                }
            },
            "required": ["text"]
        }
    })
    def input_text(self, text: str, keycode: str = "KEYCODE_TAB"):
        sanitized = (
            text.replace("%", "%%")
            .replace(" ", "%s")
            .replace('"', r'\"')
        )
        self.execute_adb(f"shell input text {sanitized}")

        # 发送按键事件移动焦点
        self.execute_adb(f"shell input keyevent {keycode}")

        return {"status": "success", "text": text, "sent_keycode": keycode}

    def get_current_package(self) -> str:
        """获取当前前台应用的包名"""
        try:
            # 获取当前焦点窗口信息
            output = self.execute_adb("shell dumpsys window windows")

            # 解析输出（适配不同Android版本）
            for line in output.splitlines():
                if "mCurrentFocus" in line or "mFocusedApp" in line:
                    # 示例格式：mCurrentFocus=Window{... com.example.app/com.example.app.MainActivity}
                    package = line.split("/")[0].split()[-1]
                    if "." in package:  # 基本包名格式验证
                        return package
            raise ValueError("未检测到前台应用")

        except Exception as e:

            raise RuntimeError("无法确定当前应用，请手动指定包名") from e

    @adb_function({
        "name": "run_monkey_test",
        "description": "执行Monkey压力测试（默认当前应用）",
        "parameters": {
            "type": "object",
            "properties": {
                "package": {
                    "type": "string",
                    "description": "应用包名（不指定则使用当前应用）"
                },
                "events": {
                    "type": "integer",
                    "default": 1000,
                    "minimum": 100,
                    "maximum": 100000
                },
                "seed": {
                    "type": "integer",
                    "description": "随机种子（用于复现测试）"
                }
            },
            "required": []
        }
    })
    def run_monkey_test(self,
                        package: Optional[str] = None,
                        events: int = 1000,
                        seed: Optional[int] = None) -> Dict:
        """执行Monkey压力测试（自动保存日志文件）"""
        try:
            # 自动获取包名（如果未指定）
            if not package:
                package = self.get_current_package()

            # 验证包名有效性
            if not self._is_package_installed(package):
                raise ValueError(f"包名 {package} 未安装")

            # 构建命令
            seed_cmd = f"-s {seed} " if seed else ""
            cmd = f"shell monkey -p {package} {seed_cmd}{events}"

            # 执行测试并获取输出
            output = self.execute_adb(cmd)

            # 生成带时间戳的日志文件名
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            log_filename = f"monkey_{timestamp}.log"

            # 保存日志文件
            log_path = os.path.join("../logs", log_filename)  # 存储在logs目录
            os.makedirs(os.path.dirname(log_path), exist_ok=True)

            try:
                with open(log_path, "w", encoding="utf-8") as f:
                    f.write(output)
                log_status = "success"
            except Exception as e:
                log_status = f"failed: {str(e)}"
                log_path = None

            return {
                "status": "completed",
                "package": package,
                "events": events,
                "log_summary": output[:500] + "..." if len(output) > 500 else output,
                "log_file": log_path,
                "log_status": log_status
            }

        except subprocess.TimeoutExpired:
            return {
                "status": "timeout",
                "message": "测试超时，可能设备无响应"
            }
        except Exception as e:
            return {
                "status": "error",
                "message": str(e),
                "package": package
            }

    def _is_package_installed(self, package: str) -> bool:
        """验证包名是否有效"""
        output = self.execute_adb(f"shell pm list packages {package}")
        return package in output

    @adb_function({
        "name": "install_app",
        "description": "安装APK文件",
        "parameters": {
            "type": "object",
            "properties": {
                "apk_path": {"type": "string", "description": "APK文件路径"}
            },
            "required": ["apk_path"]
        }
    })
    def install_app(self, apk_path: str):
        output = self.execute_adb(f"install {apk_path}")
        return {"status": "success", "output": output}

    @adb_function({
        "name": "get_package_list",
        "description": "获取设备应用包名列表（支持分类获取）",
        "parameters": {
            "type": "object",
            "properties": {
                "package_type": {
                    "type": "string",
                    "enum": ["all", "system", "third_party"],
                    "description": "包类型：all(全部)/system(系统)/third_party(第三方)",
                    "default": "all"
                },
                "include_disabled": {
                    "type": "boolean",
                    "description": "包含被禁用的应用",
                    "default": False
                },
                "filter_keyword": {
                    "type": "string",
                    "description": "包名过滤关键词（可选）"
                }
            }
        }
    })
    def get_package_list(self,
                         package_type: str = "all",
                         include_disabled: bool = False,
                         filter_keyword: Optional[str] = None) -> Dict:
        """
        增强版包名获取功能
        参数:
            package_type: 包类型 all/system/third_party
            include_disabled: 是否包含被禁用的应用
            filter_keyword: 包名关键词过滤
        返回:
            {
                "system_count": 系统应用数量,
                "third_party_count": 第三方应用数量,
                "system_packages": [系统包名列表],
                "third_party_packages": [第三方包名列表],
                "filter": {
                    "package_type": str,
                    "keyword": str,
                    "include_disabled": bool
                }
            }
        """
        try:
            # 定义ADB命令模板
            base_cmd = "shell pm list packages"
            cmd_map = {
                "all": "",
                "system": " -s",
                "third_party": " -3"
            }

            # 构建完整命令
            cmd = base_cmd + cmd_map[package_type]
            if include_disabled:
                cmd += " -u"

            # 执行命令
            output = self.execute_adb(cmd)

            # 解析基础结果
            all_packages = [
                line.split(":")[1].strip()
                for line in output.splitlines()
                if line.startswith("package:")
            ]

            # 当需要完整分类时
            if package_type == "all":
                # 获取系统包名
                system_output = self.execute_adb(base_cmd + " -s")
                system_packages = [
                    line.split(":")[1].strip()
                    for line in system_output.splitlines()
                    if line.startswith("package:")
                ]

                # 计算第三方包名
                third_party_packages = list(set(all_packages) - set(system_packages))
            else:
                system_packages = all_packages if package_type == "system" else []
                third_party_packages = all_packages if package_type == "third_party" else []

            # 应用关键词过滤
            if filter_keyword:
                lower_keyword = filter_keyword.lower()
                system_packages = [pkg for pkg in system_packages if lower_keyword in pkg.lower()]
                third_party_packages = [pkg for pkg in third_party_packages if lower_keyword in pkg.lower()]

            return {
                "system_count": len(system_packages),
                "third_party_count": len(third_party_packages),
                "system_packages": system_packages,
                "third_party_packages": third_party_packages,
                "filter": {
                    "package_type": package_type,
                    "keyword": filter_keyword,
                    "include_disabled": include_disabled
                }
            }

        except Exception as e:
            self._take_emergency_screenshot()
            return {
                "status": "error",
                "message": str(e),
                "filter": {
                    "package_type": package_type,
                    "keyword": filter_keyword,
                    "include_disabled": include_disabled
                }
            }

    @adb_function({
        "name": "terminate_app",
        "description": "终止应用运行（默认关闭当前应用）",
        "parameters": {
            "type": "object",
            "properties": {
                "package": {
                    "type": "string",
                    "description": "可选的应用包名（默认关闭当前应用）"
                },
                "clear_data": {
                    "type": "boolean",
                    "description": "是否清除应用数据",
                    "default": False
                }
            },
            "reason": {
                "type": "string",
                "description": "展示给用户的调用理由"
            },
            "required": []
        }
    })
    def terminate_app(self, package: Optional[str] = None, clear_data: bool = False) -> dict:
        """智能终止应用进程（支持默认当前应用）"""
        debug_info = {}

        try:
            # ==================== 阶段1：自动获取包名 ====================
            if not package:
                package = self.get_current_package()
                debug_info["auto_detected_package"] = package

            # ==================== 阶段2：执行终止操作 ====================
            cmd = f"shell am force-stop {package}"
            if clear_data:
                cmd += f" && pm clear {package}"

            result = self.execute_adb(cmd)
            debug_info["command_output"] = result

            # ==================== 阶段3：验证终止结果 ====================
            # verification_checks = {
            #     "is_foreground": self._is_foreground(package),
            #     "process_running": self._is_process_running(package),
            #     "in_recent_tasks": self._is_in_recent_tasks(package)
            # }
            # debug_info["verification"] = verification_checks
            #
            # if any(verification_checks.values()):
            #     raise RuntimeError(f"应用 {package} 终止不彻底")

            return {
                "status": "success",
                "package": package,
                "data_cleared": clear_data,
                "debug": debug_info
            }

        except Exception as e:
            self._take_emergency_screenshot()
            return {
                "status": "error",
                "message": str(e),
                "debug": debug_info
            }

    @adb_function({
        "name": "click_element",
        "description": "点击屏幕元素（支持多种定位方式|例如：登录，注册，忘记密码|打开应用 例如：打开抖音）",
        "parameters": {
            "type": "object",
            "properties": {
                "target": {
                    "type": "string",
                    "description": "定位目标（支持格式：'登录按钮'、'id=com.example:id/login'、'text=Submit'或'x=500,y=800'）"
                },
                "retries": {
                    "type": "integer",
                    "description": "查找重试次数",
                    "default": 3,
                    "minimum": 1
                },
                "timeout": {
                    "type": "number",
                    "description": "单次查找超时时间（秒）",
                    "default": 5,
                    "minimum": 0.5
                },
                "strategy": {
                    "type": "string",
                    "enum": ["fast", "stable"],
                    "default": "stable",
                    "description": "定位策略：fast=快速模式（可能不兼容旧设备），stable=稳定模式"
                }
            },
            "required": ["target"]
        }
    })
    def click_element(self, target: str, retries: int = 3, timeout: float = 5, strategy: str = "stable") -> dict:
        """智能元素点击功能（支持多策略定位）"""
        result = {
            "target": target,
            "attempts": [],
            "screenshot": None
        }

        try:
            # 智能解析定位指令
            locate_type, value = self._parse_target(target)

            # 执行带重试的定位点击
            for attempt in range(1, retries + 1):
                attempt_result = {
                    "attempt": attempt,
                    "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
                    "method": None
                }

                try:
                    # 获取最新界面状态
                    xml = self.dump_ui_hierarchy(strategy)
                    bounds = None
                    x, y = None, None
                    # 执行定位操作
                    if locate_type == "text":
                        bounds = self.find_element_by_text(xml, value)
                    elif locate_type == "id":
                        pass
                        # bounds = self.find_element_by_id(xml, value)
                    elif locate_type == "coord":
                        x, y = map(int, value.split(",", 1))
                    else:
                        raise ValueError(f"不支持的定位类型：{locate_type}")

                    # 计算点击坐标（坐标模式除外）
                    if locate_type != "coord":
                        x, y = self.calculate_safe_click_point(bounds)

                    # 执行点击操作
                    self.smart_tap(x, y, strategy)

                    # 记录成功结果
                    attempt_result.update({
                        "status": "success",
                        "method": locate_type,
                        "coordinates": (x, y)
                    })
                    result.update(attempt_result)
                    result["status"] = "success"
                    return result

                except ElementNotFound as e:
                    # 记录失败尝试
                    attempt_result["status"] = "retry"
                    attempt_result["reason"] = str(e)
                    result["attempts"].append(attempt_result)

                    # 最后一次尝试直接抛出异常
                    if attempt == retries:
                        raise

                    # 动态等待时间（指数退避）
                    sleep_time = min(timeout, (1.5 ** attempt) * 0.5)
                    time.sleep(sleep_time)

        except Exception as e:
            result.update({
                "status": "error",
                "error_type": e.__class__.__name__,
                "message": str(e)
                # "screenshot": self.capture_debug_screenshot()
            })
        finally:
            # 保留最后一次界面状态
            result["last_ui_xml"] = self.get_cached_ui_hierarchy()

        return result

    def get_cached_ui_hierarchy(self) -> str:
        """获取最后一次缓存的界面布局XML"""
        return getattr(self, "_cached_ui_xml", "<?xml version='1.0' encoding='UTF-8'?>")

    def _parse_target(self, target: str) -> tuple:
        """智能解析定位指令"""
        # 显式指定类型的情况
        if re.match(r"^(id|text|x)=.+", target):
            locate_type, value = target.split("=", 1)
            if locate_type == "x":  # 坐标别名处理
                locate_type = "coord"
            return (locate_type, value)

        # 智能推断定位类型
        if re.match(r"^\d+,\d+$", target):
            return ("coord", target)

        # 常见控件文本映射
        text_mapping = {
            "登录按钮": "登录",
            "注册按钮": "注册",
            "确定按钮": "确定"
        }
        return ("text", text_mapping.get(target, target))

    def dump_ui_hierarchy(self, strategy: str) -> str:
        """获取界面布局XML（带缓存策略）"""
        if strategy == "fast" and hasattr(self, "_cached_ui_xml"):
            return self._cached_ui_xml

        tmp_xml = "/sdcard/window_dump.xml"
        self.execute_adb(f"shell uiautomator dump --compressed {tmp_xml}")
        self._cached_ui_xml = self.execute_adb(f"shell cat {tmp_xml}")
        return self._cached_ui_xml

    def find_element_by_text(self, xml: str, text: str) -> tuple:
        """改进的文本元素查找"""
        try:
            from xml.etree import ElementTree as ET
        except ImportError:
            raise RuntimeError("需要XML解析库支持")

        root = ET.fromstring(xml)
        for node in root.iter('node'):
            if node.get('text') == text and node.get('clickable') == 'true':
                bounds = node.get('bounds')
                return self.parse_bounds(bounds)
        raise ElementNotFound(f"未找到可点击的文本元素：{text}")

    def parse_bounds(self, bounds_str: str) -> tuple:
        """安全解析坐标边界"""
        try:
            nums = list(map(int, re.findall(r'\d+', bounds_str)))
            if len(nums) != 4:
                raise ValueError
            return (nums[0], nums[1], nums[2], nums[3])
        except:
            raise ElementNotFound("无效的坐标边界格式")

    def calculate_safe_click_point(self, bounds: tuple) -> tuple:
        """带安全边界的点击点计算"""
        x1, y1, x2, y2 = bounds
        width = x2 - x1
        height = y2 - y1

        # 排除无效区域
        if width < 5 or height < 5:
            raise ElementNotFound("元素可视区域过小")

        # 计算中心点（偏移10%防止点击边缘）
        offset_w = int(width * 0.1)
        offset_h = int(height * 0.1)
        return (
            random.randint(x1 + offset_w, x2 - offset_w),
            random.randint(y1 + offset_h, y2 - offset_h)
        )

    def smart_tap(self, x: int, y: int, strategy: str):
        """智能点击策略"""
        if strategy == "fast":
            self.execute_adb(f"shell input tap {x} {y}")
        else:
            # 稳定模式：模拟真实点击行为
            self.execute_adb(f"shell input swipe {x} {y} {x} {y} 100")

    @adb_function({
        "name": "launch_app",
        "description": "启动指定应用到前台（增强版活动解析）",
        "parameters": {
            "type": "object",
            "properties": {
                "package": {
                    "type": "string",
                    "description": "需要启动的应用包名"
                },
                "activity": {
                    "type": "string",
                    "description": "可选的主活动名称（默认自动解析）"
                },
                "force_restart": {
                    "type": "boolean",
                    "default": False,
                    "description": "是否强制冷启动（默认热启动）"
                }
            },
            "required": ["package"]
        }
    })
    def launch_app(self, package: str, activity: Optional[str] = None, force_restart: bool = False) -> dict:
        """通过ADB启动Android应用（增强解析逻辑）"""
        try:
            # 增强版活动解析
            if not activity:
                # 方法1：使用更可靠的解析方式
                resolve_cmd = f"shell cmd package resolve-activity -c android.intent.category.LAUNCHER {package}"
                result = self.execute_adb(resolve_cmd)

                # 新增正则表达式匹配
                activity_match = re.search(r'name=([^\s/]+/[^\s]+)', result)
                if activity_match:
                    activity = activity_match.group(1)
                else:
                    # 方法2：备用解析方案
                    dump_cmd = f"shell pm dump {package} | grep -A1 MAIN"
                    dump_output = self.execute_adb(dump_cmd)
                    activity = re.findall(r'([^\s/]+/[^\s]+)', dump_output)[0]

                # 最终验证
                if "/" not in activity:
                    raise ValueError(f"解析到无效的活动名: {activity}")

            # 完整的启动逻辑
            # start_cmd = ["am", "start", "-n", "com.taobao.taobao/com.taobao.tao.TBMainActivity"]
            start_cmd = f"shell am start -n {activity}"
            self.execute_adb(start_cmd)
            # start_cmd.append("--force-stop")
        except Exception as e:
            return {
                "status": "error",
                "message": f"启动失败: {str(e)}",
                "package": package,
                "activity": activity or "N/A"
            }

    @adb_function({
        "name": "get_devices",
        "description": "获取所有连接的ADB设备列表",
        "parameters": {
            "type": "object",
            "properties": {}
        }
    })
    def get_devices(self):
        """获取当前连接的所有ADB设备信息"""
        output = self.execute_adb("devices")
        devices = []

        # 解析adb devices输出
        for line in output.splitlines():
            line = line.strip()

            # 跳过无关行（守护进程状态/标题行/空行）
            if not line or line.startswith("*") or line == "List of devices attached":
                continue

            # 分割设备ID和状态
            parts = line.split()
            if len(parts) >= 2:
                devices.append({
                    "device_id": parts[0],
                    "status": parts[1].lower()
                })

        return {"devices": devices}

    @staticmethod
    def get_connected_devices():
        return DeviceManager().get_devices()

    @staticmethod
    def check_connection(self):
        """通过执行简单命令验证连接"""
        try:
            result = self.execute_adb("echo check_connection")
            return "check_connection" in result
        except:
            return False

    @adb_function({
        "name": "get_logcat_lines",
        "description": "获取指定行数的设备日志",
        "parameters": {
            "type": "object",
            "properties": {
                "line_count": {
                    "type": "integer",
                    "description": "要输出的日志行数",
                    "default": 10
                },
                "save_path": {
                    "type": "string",
                    "description": "日志保存路径（可选）",
                    "default": ""
                }
            }
        }
    })
    def get_logcat_lines(self, line_count: int = 10, save_path: str = ""):
        command = f"shell logcat -t {line_count}"
        if save_path:
            self.execute_adb(f"{command} > {save_path}")
            return {"path": save_path}
        else:
            output = self.execute_adb(command)
            return {"output": output}

    @adb_function({
        "name": "get_android_version",
        "description": "获取设备Android系统版本",
        "parameters": {"type": "object", "properties": {}}
    })
    def get_android_version(self):
        output = self.execute_adb("shell getprop ro.build.version.release")
        return {"version": output.strip()}

    @adb_function({
        "name": "get_screen_resolution",
        "description": "获取设备屏幕分辨率",
        "parameters": {
            "type": "object",
            "properties": {
                "parse_as_dict": {
                    "type": "boolean",
                    "description": "是否解析为字典格式（默认True）",
                    "default": True
                }
            }
        }
    })
    def get_screen_resolution(self, parse_as_dict: bool = True):
        output = self.execute_adb("shell wm size")
        # 解析输出（示例：Physical size: 1080x1920）
        resolution = output.split(": ")[-1].strip().split("x")
        if parse_as_dict:
            return {
                "width": int(resolution[0]),
                "height": int(resolution[1])
            }
        else:
            return {"raw_output": output}

    @adb_function({
        "name": "get_cpu_info",
        "description": "获取设备CPU核心信息",
        "parameters": {
            "type": "object",
            "properties": {
                "detail_level": {
                    "type": "string",
                    "description": "信息详细等级（basic/full）",
                    "default": "basic"
                }
            }
        }
    })
    def get_cpu_info(self, detail_level: str = "basic"):
        output = self.execute_adb("shell cat /proc/cpuinfo")

        # 核心数据解析
        cores = 0
        model = ""
        architecture = ""
        for line in output.splitlines():
            if "processor" in line:
                cores += 1
            elif "model name" in line or "Processor" in line:
                model = line.split(":")[-1].strip()
            elif "CPU architecture" in line:
                architecture = line.split(":")[-1].strip()

        result = {
            "cores": cores,
            "model": model,
            "architecture": f"ARMv{architecture}" if architecture else "Unknown"
        }

        if detail_level == "full":
            result["raw_data"] = output

        @adb_function({
            "name": "dump_package_info",
            "description": "获取Android应用的详细信息并结构化解析",
            "parameters": {
                "type": "object",
                "properties": {
                    "package_name": {
                        "type": "string",
                        "description": "目标应用的包名（必填）"
                    },
                    "detail_level": {
                        "type": "string",
                        "description": "信息详细等级（basic/full）",
                        "default": "basic"
                    }
                },
                "required": ["package_name"]
            }
        })
        def dump_package_info(self, package_name: str, detail_level: str = "basic"):
            # 执行pm dump命令
            raw_output = self.execute_adb(f"shell pm dump {package_name}")

            # 核心数据解析（示例解析关键字段）
            parsed_data = {
                "package": package_name,
                "version": "",
                "install_path": "",
                "permissions": [],
                "activities": [],
                "services": [],
                "receivers": []
            }

            # 解析版本号[2,6](@ref)
            version_line = next((line for line in raw_output.splitlines() if "versionName" in line), "")
            parsed_data["version"] = version_line.split("=")[-1].strip() if version_line else "Unknown"

            # 解析安装路径[3,7](@ref)
            code_path_line = next((line for line in raw_output.splitlines() if "codePath=" in line), "")
            parsed_data["install_path"] = code_path_line.split("=")[-1].strip() if code_path_line else ""

            # 解析权限列表[2,6](@ref)
            in_permission_section = False
            for line in raw_output.splitlines():
                if "Declared Permissions:" in line:
                    in_permission_section = True
                    continue
                if in_permission_section and "  " in line and "permission:" in line:
                    permission = line.split(":")[1].strip()
                    parsed_data["permissions"].append(permission)

            # 解析Activity组件[3,7](@ref)
            activity_section = re.search(r"Activity Resolver Table:(.*?)\n\n", raw_output, re.DOTALL)
            if activity_section:
                parsed_data["activities"] = [
                    line.split(" ")[1] for line in activity_section.group(1).splitlines()
                    if "filter" in line
                ]

            # 根据详细等级返回数据
            result = {"basic_info": parsed_data}
            if detail_level == "full":
                result["raw_data"] = raw_output

            return result
        return result
if __name__ == "__main__":
    print(DeviceManager().get_connected_devices())
